﻿using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Timers;
using ClosedXML.Excel;
using MFilesAPI;
using Timer = System.Timers.Timer;

namespace FSAG.UserImporter
{
    /// <summary>
    /// General Import manager which calls the underlying user and group managers and is responsible for the import procedure
    /// </summary>
    public static class ImportManager
    {
        private static int _numberCheckoutTries = ConfigManager.GetSettingAsNumber("NumberCheckoutTries");
        private static readonly ObjIDClass AccountListID = new ObjIDClass {ID = ConfigManager.GetSettingAsNumber("ObjIDUserList"), Type = 0};

        private static MFilesServerApplication _mFilesServerApplication;
        private static Timer _checkoutRetryTimer;
        private static Vault _academiesVault;
        private static string _path;
        private static int _checkOutRetryCounter;
        private static readonly AutoResetEvent AutoEvent = new AutoResetEvent(false);
        private static XLWorkbook _workbook;
        private static ObjectFile _fileObject;

        /// <summary>
        /// This method is the main entry for the whole import procedure. It is responsible for initializing and downloading
        /// the file and furthermore calling the import methods on the group and user manager
        /// </summary>
        public static void StartImportProcedure()
        {
            try
            {
                InitializeProgram();
                InitializeMFConnection();

                TryExecuteMethod(DownloadExcelFile, TryDownloadExcelFile);
                _workbook = new XLWorkbook(_path);

                var modifiedUsersExist = UserManager.StartImportUsers(_mFilesServerApplication, _academiesVault, _path);
                var modifiedGroupsExist = GroupManager.StartImportGroups(_mFilesServerApplication, _academiesVault, _path);

                if (!modifiedGroupsExist && !modifiedUsersExist)
                {
                    ConfigManager.Log.Info("Keine Benutzer oder Gruppen zum Aktualisieren!");
                    return;
                }
                UserManager.StartAssignGroups();
                UserManager.FinalizeUserImport(_workbook);
                GroupManager.FinalizeGroupImport(_workbook);

                ConfigManager.Log.Info("Änderungen werden gespeichert");
                _workbook.Save();
                ConfigManager.Log.Info("Benutzerliste wurde gespeichert");

                TryExecuteMethod(UploadExcelFile, TryUploadExcelFile);
                ConfigManager.Log.Info("Aktualisierung der Benutzer & Gruppen war erfolgreich!");
            }
            catch (Exception e)
            {
                ConfigManager.Log.Fatal("Beim ausfuehren des User-Importers ist ein Fehler aufgetreten:",e);
                Thread.Sleep(3000);
            }
            finally
            {
                if (_mFilesServerApplication != null)
                {
                    _mFilesServerApplication.Disconnect();
                }
                DeleteFileIfExists(_path);
            }
        }

        /// <summary>
        /// Helper method which is in charge of executing a given method and if 
        /// the result is false periodically with a timer a handler is called
        /// </summary>
        /// <param name="method">The method which is called</param>
        /// <param name="retryHandler">If method call wasn't successful the retryhandler is executed</param>
        private static void TryExecuteMethod(Func<bool> method, ElapsedEventHandler retryHandler)
        {
            if (method == null) return;

            if (!method())
            {
                if (_checkoutRetryTimer == null)
                {
                    _numberCheckoutTries = ConfigManager.GetSettingAsNumber("NumberCheckoutTries");
                    _checkoutRetryTimer = new Timer(ConfigManager.GetSettingAsNumber("CheckoutRetryTimerIntervall"));
                    _checkoutRetryTimer.Elapsed += retryHandler;
                    _checkoutRetryTimer.Start();
                    AutoEvent.WaitOne(60000);

                    if (_numberCheckoutTries <= 0)
                    {
                        throw new ApplicationException("Das Excel Dokument konnte nicht ausgechecked werden. Checken Sie das Dokument ein, bevor der User-Importer ausgeführt wird.");
                    }
                }
            }
            else
            {
                if (_checkoutRetryTimer != null)
                {
                    _checkoutRetryTimer.Stop();
                    _checkoutRetryTimer = null;
                }
                _checkOutRetryCounter = 0;
                AutoEvent.Set();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void TryDownloadExcelFile(object sender, ElapsedEventArgs e)
        {
            _checkOutRetryCounter++;

            ConfigManager.Log.InfoFormat("{0}. Versuch Dokument auszuchecken", _checkOutRetryCounter);
            TryExecuteMethod(DownloadExcelFile,TryDownloadExcelFile);
            
            if (--_numberCheckoutTries <= 0)
            {
                _checkoutRetryTimer.Stop();
                AutoEvent.Set();
            }
        }

        /// <summary>
        /// EventHandler which is periodically called until the excel file could be uploaded or the retrycounter has reached the 
        /// defined maximum retries if the first upload wasn't successful
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private static void TryUploadExcelFile(object sender, ElapsedEventArgs e)
        {
            _checkOutRetryCounter++;
            ConfigManager.Log.InfoFormat("{0}. Versuch Dokument auszuchecken", _checkOutRetryCounter);
            TryExecuteMethod(UploadExcelFile, TryUploadExcelFile);

            if (--_numberCheckoutTries <= 0)
            {
                _checkoutRetryTimer.Stop();
                AutoEvent.Set();
            }
        }

        /// <summary>
        /// Checks wheter a file under the given path exists and if so the file is deleted
        /// </summary>
        /// <param name="path">The location to check for a excel file</param>
        private static void DeleteFileIfExists(string path)
        {
            if (!string.IsNullOrWhiteSpace(path) && File.Exists(path))
            {
                File.Delete(_path);
                ConfigManager.Log.Debug("Lokale Datei wurde erfolgreich gelöscht");
            }
        }

        /// <summary>
        /// Method used for uploading the excel file (defined in the app.config)
        /// </summary>
        /// <returns>Returns true, if the excel file was successfully uploaded</returns>
        private static bool UploadExcelFile()
        {
            var version = CheckOutExcelFile();
            if (version == null) return false;
            _academiesVault.ObjectFileOperations.UploadFile(_fileObject.ID, version.ObjVer.Version,_path);
            _academiesVault.ObjectOperations.CheckIn(version.ObjVer);

            ConfigManager.Log.Info("Benutzerliste wurde eingechecked");
            DeleteFileIfExists(_path);
            ConfigManager.Log.Info("Dokument wurde lokal wieder gelöscht");
            return true;
        }

        /// <summary>
        /// Method used for downloading the excel file (defined in the app.config)
        /// </summary>
        /// <returns>Returns true, if the excel file was successfully downloaded</returns>
        private static bool DownloadExcelFile()
        {
            var version = CheckOutExcelFile();
            if (version == null) return false;
            version = _academiesVault.ObjectOperations.CheckIn(version.ObjVer);

            ConfigManager.Log.Info("Benutzerliste wurde eingechecked");
            _academiesVault.ObjectFileOperations.DownloadFile(_fileObject.ID, version.ObjVer.Version, _path);
            ConfigManager.Log.Info("Benutzerliste konnte heruntergeladen werden");
            return true;
        }

        /// <summary>
        /// Helper method for checking out the configured excel file
        /// </summary>
        /// <returns>The objectversion instance or null if the excel file couldn't be checked out</returns>
        private static ObjectVersion CheckOutExcelFile()
        {
            try
            {
                var version = _academiesVault.ObjectOperations.CheckOut(AccountListID);
                var files = _academiesVault.ObjectFileOperations.GetFiles(version.ObjVer);
                if (files.Count == 0) return null;
                foreach (ObjectFile objectFile in files)
                {
                    _fileObject = objectFile;
                    break;
                }
                ConfigManager.Log.Info("Benutzerliste wurde ausgechecked");
                return version;
            }
            catch (Exception)
            {
                return null;
            }
        }

        /// <summary>
        /// Initializes the program especially the excel query initializer nad path variables
        /// </summary>
        private static void InitializeProgram()
        {
            Console.WriteLine("================");
            Console.WriteLine("User-Importer");
            Console.WriteLine("================");
            Console.WriteLine();

            string dataPath = Assembly.GetCallingAssembly().Location;
            dataPath = Directory.GetParent(dataPath).FullName;
            _path = dataPath + "\\" + ConfigManager.GetSetting("TempFileName");
            ConfigManager.Log.DebugFormat("Exceldatei wird unter dem Pfad {0} abgelegt",_path);

            ExcelQueryInitializer.Initialize(_path);
            ConfigManager.Log.Info("Programm wurde initialisiert");
        }

        /// <summary>
        /// Used to initialize the connection to the mfiles vault configured under the app.config
        /// </summary>
        private static void InitializeMFConnection()
        {
            _mFilesServerApplication = new MFilesServerApplication();

            _mFilesServerApplication.Connect(MFAuthType.MFAuthTypeSpecificMFilesUser, "ATK.sysadm", "F@IGéSoL123", "",
                                             ConfigManager.GetSetting("Protocol"), ConfigManager.GetSetting("ServerDNS"),
                                             ConfigManager.GetSetting("Port"));

            _academiesVault = _mFilesServerApplication.LogInAsUserToVault(ConfigManager.GetSetting("VaultID"),
                                                                          null,
                                                                          MFAuthType.MFAuthTypeSpecificMFilesUser,
                                                                          "ATK.sysadm", "F@IGéSoL123");

            ConfigManager.Log.Info("Verbindung mit Vault wurde aufgebaut");
        }
    }
}